use std::sync::{Arc, LazyLock};

use arc_swap::ArcSwapOption;
use parking_lot::Mutex;
use tokio::sync::oneshot::Sender;

use crate::utils::{lru::Lru, unique_id::UniqueID};

pub struct ServerConnCancelToken(Sender<()>);

#[allow(dead_code)]
impl ServerConnCancelToken {
    #[inline]
    pub fn new(sender: Sender<()>) -> Self {
        ServerConnCancelToken(sender)
    }

    #[inline]
    pub fn cancel(self) {
        _ = self.0.send(());
    }
}

#[allow(dead_code)]
static SERVER_SESSION_POOL: LazyLock<ArcSwapOption<ServerConnPool>> =
    LazyLock::new(|| ArcSwapOption::new(None));

#[allow(dead_code)]
pub struct ServerConnPool {
    lru: Mutex<Lru<UniqueID, ServerConnCancelToken>>,
    capacity: usize,
}

#[allow(dead_code)]
impl ServerConnPool {
    pub fn init(capacity: usize) -> Arc<ServerConnPool> {
        let res = Arc::new(ServerConnPool {
            lru: Mutex::new(Lru::with_capacity(1, capacity)),
            capacity,
        });

        SERVER_SESSION_POOL.store(Some(res.clone()));
        res
    }

    #[inline]
    pub fn get_pool() -> Option<Arc<ServerConnPool>> {
        SERVER_SESSION_POOL.load().clone()
    }

    #[inline]
    pub fn get_len() -> usize {
        let pool = SERVER_SESSION_POOL.load();
        match pool.as_ref() {
            Some(r) => {
                let guard = r.lru.lock();
                guard.len()
            }
            None => 0,
        }
    }

    pub fn add_cancel_token(id: UniqueID, cancel_token: ServerConnCancelToken) {
        let pool = SERVER_SESSION_POOL.load();
        match pool.as_ref() {
            Some(p) => {
                if p.capacity == 0 {
                    cancel_token.cancel();
                    return;
                }

                let old_session = p.lru.lock().insert(id, cancel_token);
                if let Some(s) = old_session {
                    s.cancel();
                }
            }

            None => {
                cancel_token.cancel();
            }
        }
    }

    pub fn remove_cance_token(id: UniqueID) {
        let pool = SERVER_SESSION_POOL.load();
        if let Some(p) = pool.as_ref() {
            p.lru.lock().remove(id);
        }
    }

    pub fn evict() {
        let pool = SERVER_SESSION_POOL.load();
        if let Some(p) = pool.as_ref() {
            let last = p.lru.lock().evict();
            if let Some(l) = last {
                l.cancel();
            }
        }
    }
}

#[cfg(test)]
mod tests {

    use std::{sync::atomic::Ordering, time::Duration};
    use tokio::{
        select,
        sync::oneshot,
        time::{sleep, timeout},
    };

    use crate::{
        http::pool::server_pool::ServerConnCancelToken, runtime::*,
        utils::unique_id::UniqueID,
    };

    use super::ServerConnPool;

    #[test]
    fn test_server_pool() {
        RuntimeManager::init_main_runtime(1);
        RuntimeManager::init_worker_runtime(None, 1);

        ServerConnPool::init(10);

        RuntimeManager::main_runtime_block_on(async {
            WAIT_TASKS.fetch_add(100, Ordering::Relaxed);

            for _ in 0..100 {
                RuntimeManager::get_worker_runtime()
                    .get_handle()
                    .spawn(async {
                        let id = UniqueID::new();
                        let (sender, receiver) = oneshot::channel::<()>();
                        ServerConnPool::add_cancel_token(id, ServerConnCancelToken::new(sender));

                        select! {
                            res = timeout(Duration::from_millis(100), async{
                                sleep(Duration::from_millis(200)).await;
                            }) => {
                                match res {
                                    Ok(_) => {
                                        println!("success: {}", id.crc64());
                                    },
                                    Err(e) => {
                                        println!("timedout: {}, {e}", id.crc64());
                                    },
                                }
                            },
                            _ = receiver => {
                                println!("cancel: {}", id.crc64());
                            }
                        };

                        ServerConnPool::remove_cance_token(id);

                        let t = WAIT_TASKS.fetch_sub(1, Ordering::Relaxed);
                        if t - 1 == 0 {
                            PROCESS_EXIT.store(true, Ordering::Relaxed);
                        }
                    });
            }

            let mut exit = false;
            loop {
                if PROCESS_EXIT.load(Ordering::Relaxed) {
                    println!("process done");
                    exit = true;
                }

                sleep(Duration::from_millis(100)).await;
                if exit {
                    break;
                }
            }
        })
    }
}
